package com.song.demo.webmvc.aop;

import jdk.nashorn.internal.ir.CallNode;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 主要用来解析和封装AOP配置
 */
public class GPAdvisedSupport {
    private Class targetClass;

    private Object target;

    private Pattern pointCutClassPattern;

    private transient Map<Method, List<Object>> methodCache;

    private GPAopConfig aopConfig;

    public void setTarget(Object target){
        this.target = target;
    }
    
    public GPAdvisedSupport(GPAopConfig aopConfig){
        this.aopConfig = aopConfig;
    }

    public Object getTarget(){
        return this.target;
    }
    
    public Class getTargetClass(){
        return  this.targetClass;
    }
    public void setTargetClass(Class targetClass){
        this.targetClass = targetClass;
        parse();
    }

    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method,Class<?> targetClass) throws NoSuchMethodException {

        List<Object> cached = this.methodCache.get(method);
        if (cached == null){

            Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
            cached = methodCache.get(m);
            this.methodCache.put(m,cached);
        }
        return cached;
    }

    public boolean pointCutMatch(){
        String s = this.targetClass.toString();
        return pointCutClassPattern.matcher(s).matches();
    }





    private void parse() {
        //pointCut表达式
        String pointCut = this.aopConfig.getPointCut().replaceAll("\\.","\\\\.")
                .replaceAll("\\\\.\\*",".*")
                .replaceAll("\\(","\\\\(")
                .replaceAll("\\)","\\\\)");
        String pointCutForClass = pointCut.substring(0,pointCut.lastIndexOf("\\)")-4);
        this.pointCutClassPattern = Pattern.compile("class" + pointCutForClass.substring(pointCutForClass.lastIndexOf(" ")-1));
        methodCache = new  HashMap<Method, List<Object>>();
        Pattern pattern = Pattern.compile(pointCut);
        try{
            Class<?> aspectClass = Class.forName(aopConfig.getAspectClass());
            HashMap<String, Method> aspectMethods = new HashMap<>();
            for (Method method : aspectClass.getMethods()) {
                aspectMethods.put(method.getName(),method);
            }
            //在这里得到的方法都是原生方法
            for (Method method : targetClass.getMethods()) {
                String methodString = method.toString();
                if (methodString.contains("throws")){
                    methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim();
                }
                Matcher matcher = pattern.matcher(methodString);
                if (matcher.matches()){
                    //能满足切面规则的类，添加到aop配置中
                    LinkedList<Object> advices = new LinkedList<>();
                    //前置通知
                    if (aopConfig.getAspectBefore() != null && !aopConfig.getAspectBefore().equals("")){
                        advices.add(new GPBeforeAdvice(aspectMethods.get(
                                aopConfig.getAspectBefore()
                        ),aspectClass.newInstance()));
                    }
                    //后置通知
                    if (aopConfig.getAspectAfter() != null && !aopConfig.getAspectAfter().equals("")){
                        advices.add(new GPAfterReturn(aspectMethods.get(
                                aopConfig.getAspectAfter()
                        ),aspectClass.newInstance()));
                    }
                    //异常通知
                    if (aopConfig.getAspectAfterThrow() != null && !aopConfig.getAspectAfterThrow().equals("")){
                        GPAfterThrowingAdvice afterThrowingAdvice = new GPAfterThrowingAdvice(aspectMethods.get(
                                aopConfig.getAspectAfterThrow()
                        ), aspectClass.newInstance());
                        afterThrowingAdvice.setThrowName(aopConfig.getAspectAfterThrowingName());
                        advices.add(afterThrowingAdvice);
                    }
                    methodCache.put(method,advices);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }


}
